#include "XVulkan.h"
#include "stb_image_aug.h"
#pragma comment(lib, "vulkan-1.lib")

#define MSAA 1

static XTexture *sDefaultTexture = nullptr;
static VkCommandBuffer sMainCommandBuffer = VK_NULL_HANDLE;
static float sClearColor[] = {0.1f, 0.4f, 0.6f, 1.0f};
static float sClearDepth = 1.0f;
static uint32_t sClearStencil = 1.0f;
static VkRenderPass sRenderPass = VK_NULL_HANDLE;
static XTexture *sColorBuffer = nullptr;
static XTexture *sDepthBuffer = nullptr;
static int sFrameBufferCount = 0;
static int sViewportWidth = 0;
static int sViewportHeight = 0;
static XSystemFrameBuffer *sSystemFrameBuffer = nullptr;
static VkCommandPool sCommandPool;
static VkSemaphore sReadyToRender, sReadyToPresent;
static VkSwapchainKHR sSwapChain = VK_NULL_HANDLE;
static VkFormat sSwapChainImageFormat = VK_FORMAT_B8G8R8A8_UNORM;
static VkExtent2D sSwapChainExtent;
static std::vector<VkImage> sSwapChainImages;
static std::vector<VkImageView> sSwapChainImageViews;
static VkDevice sVulkanDevice;
static VkQueue sGraphicQueue, sPresentQueue;
static VkPhysicalDevice sPhysicalDevice;
static VkSampleCountFlagBits sMaxSampleCount;
static int sGraphicQueueFamily;
static int sPresentQueueFamily;
static void *sWindowHWND = nullptr;
static VkInstance sVulkanInstance = VK_NULL_HANDLE;
static VkSurfaceKHR sVulkanSurface = VK_NULL_HANDLE;
static VkDebugReportCallbackEXT sVulkanDebugger;

static PFN_vkCreateDebugReportCallbackEXT __vkCreateDebugReportCallbackEXT = nullptr;
static PFN_vkDestroyDebugReportCallbackEXT __vkDestroyDebugReportCallbackEXT = nullptr;
static PFN_vkCreateWin32SurfaceKHR __vkCreateWin32SurfaceKHR = nullptr;
static uint32_t sCurrentRenderFrameBufferIndex = 0;

XBufferObject::XBufferObject() : mBuffer(VK_NULL_HANDLE), mMemory(VK_NULL_HANDLE)
{
}

XBufferObject::~XBufferObject()
{
    if (mBuffer)
    {
        vkDestroyBuffer(xGetVulkanDevice(), mBuffer, nullptr);
    }

    if (mMemory)
    {
        vkFreeMemory(xGetVulkanDevice(), mMemory, nullptr);
    }
}

void XBufferObject::OnSetSize()
{
    if (mType == kXBufferObjectTypeVertexBuffer)
    {
        xGenVertexBuffer(GetSize(), mBuffer, mMemory);
    }
    else if (mType == kXBufferObjectTypeIndexBuffer)
    {
        xGenIndexBuffer(GetSize(), mBuffer, mMemory);
    }
    else if (mType == kXBufferObjectTypeUniformBuffer)
    {
        xGenBuffer(mBuffer, mMemory, GetSize(), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
    }
}

void XBufferObject::SubmitData(const void *data, int size)
{
    if (mType == kXBufferObjectTypeVertexBuffer)
    {
        xBufferSubVertexData(mBuffer, data, size);
    }
    else if (mType == kXBufferObjectTypeIndexBuffer)
    {
        xBufferSubIndexData(mBuffer, data, size);
    }
    else if (mType == kXBufferObjectTypeUniformBuffer)
    {
        void *dst;
        vkMapMemory(xGetVulkanDevice(), mMemory, 0, size, 0, &dst);
        memcpy(dst, data, size);
        vkUnmapMemory(xGetVulkanDevice(), mMemory);
    }
}

int XBufferObject::GetSize()
{
    return 0;
}

VkResult xGenBuffer(VkBuffer &buffer, VkDeviceMemory &bufferMemory, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties)
{
    VkBufferCreateInfo bufferInfo{};
    bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    bufferInfo.size = size;
    bufferInfo.usage = usage;

    VkResult ret = vkCreateBuffer(xGetVulkanDevice(), &bufferInfo, nullptr, &buffer);
    if (ret != VK_SUCCESS)
    {
        printf("failed to create buffer\n");
        return ret;
    }

    VkMemoryRequirements requirements{};
    vkGetBufferMemoryRequirements(xGetVulkanDevice(), buffer, &requirements);

    VkMemoryAllocateInfo memoryAllocInfo{};
    memoryAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
    memoryAllocInfo.allocationSize = requirements.size;
    memoryAllocInfo.memoryTypeIndex = xGetMemoryType(requirements.memoryTypeBits, properties);

    ret = vkAllocateMemory(xGetVulkanDevice(), &memoryAllocInfo, nullptr, &bufferMemory);
    if (ret != VK_SUCCESS)
    {
        printf("failed to alloc memory\n");
        return ret;
    }

    ret = vkBindBufferMemory(xGetVulkanDevice(), buffer, bufferMemory, 0);
    if (ret != VK_SUCCESS)
    {
        printf("failed to bind memory\n");
        return ret;
    }

    return VK_SUCCESS;
}

void xBufferSubData(VkBuffer buffer, VkBufferUsageFlags usage, const void *data, VkDeviceSize size)
{
    VkBuffer tempBuffer;
    VkDeviceMemory tempMemory;

    xGenBuffer(tempBuffer, tempMemory, size, usage, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);

    void *host_memory;
    vkMapMemory(xGetVulkanDevice(), tempMemory, 0, size, 0, &host_memory);
    memcpy(host_memory, data, (size_t)size);
    vkUnmapMemory(xGetVulkanDevice(), tempMemory);

    VkCommandBuffer commandBuffer;
    xBeginOneTimeCommandBuffer(&commandBuffer);

    VkBufferCopy copy = {0, 0, size};
    vkCmdCopyBuffer(commandBuffer, tempBuffer, buffer, 1, &copy);

    xEndOneTimeCommandBuffer(commandBuffer);

    vkDestroyBuffer(xGetVulkanDevice(), tempBuffer, nullptr);
    vkFreeMemory(xGetVulkanDevice(), tempMemory, nullptr);
}

uint32_t xGetMemoryType(uint32_t type_filters, VkMemoryPropertyFlags properties)
{
    VkPhysicalDeviceMemoryProperties memory_properties;
    vkGetPhysicalDeviceMemoryProperties(xGetVulkanPhysicalDevice(), &memory_properties);

    for (uint32_t i = 0; i < memory_properties.memoryTypeCount; ++i)
    {
        uint32_t flag = i << i;
        if (flag & type_filters && (memory_properties.memoryTypes[i].propertyFlags & properties) == properties)
        {
            return i;
        }
    }
    return 0;
}

void xBeginOneTimeCommandBuffer(VkCommandBuffer *commandBuffer)
{
    xGenCommandBuffer(commandBuffer, 1);

    VkCommandBufferBeginInfo cbbi{};
    cbbi.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
    cbbi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;

    vkBeginCommandBuffer(*commandBuffer, &cbbi);
}

void xEndOneTimeCommandBuffer(VkCommandBuffer commandBuffer)
{
    vkEndCommandBuffer(commandBuffer);
    xWaitForCommandFinish(commandBuffer);
    vkFreeCommandBuffers(xGetVulkanDevice(), xGetCommandPool(), 1, &commandBuffer);
}

void xGenCommandBuffer(VkCommandBuffer *commandBuffer, int count, VkCommandBufferLevel level)
{
    VkCommandBufferAllocateInfo cbai{};
    cbai.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
    cbai.level = level;
    cbai.commandPool = xGetCommandPool();
    cbai.commandBufferCount = count;

    VkResult ret = vkAllocateCommandBuffers(xGetVulkanDevice(), &cbai, commandBuffer);
    if (ret != VK_SUCCESS)
    {
        printf("failed to alloc commandbuffer\n");
    }
}

void xWaitForCommandFinish(VkCommandBuffer commandBuffer)
{
    VkSubmitInfo submitInfo{};
    submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
    submitInfo.commandBufferCount = 1;
    submitInfo.pCommandBuffers = &commandBuffer;

    VkFenceCreateInfo fci{};
    fci.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;

    VkFence fence;
    vkCreateFence(xGetVulkanDevice(), &fci, nullptr, &fence);

    vkQueueSubmit(xGetGraphicQueue(), 1, &submitInfo, fence);
    vkWaitForFences(xGetVulkanDevice(), 1, &fence, VK_TRUE, 100000000);
    vkDestroyFence(xGetVulkanDevice(), fence, nullptr);
}

void xCreateShader(VkShaderModule &shader, unsigned char *code, int code_len)
{
    VkShaderModuleCreateInfo smci{};
    smci.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
    smci.codeSize = code_len;
    smci.pCode = (uint32_t *)code;
    vkCreateShaderModule(xGetVulkanDevice(), &smci, nullptr, &shader);
}

void xAttachVertexShader(XProgram *program, VkShaderModule shader)
{
    program->mShaderStage[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
    program->mShaderStage[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
    program->mShaderStage[0].module = shader;
    program->mShaderStage[0].pName = "main";
    program->mVertexShader = shader;
}

void xAttachFragmentShader(XProgram *program, VkShaderModule shader)
{
    program->mShaderStage[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
    program->mShaderStage[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
    program->mShaderStage[1].module = shader;
    program->mShaderStage[1].pName = "main";
    program->mFragmentShader = shader;
}

void xInitDescriptorSetLayout(XProgram *program)
{
    VkDescriptorSetLayoutCreateInfo dslci{};
    dslci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
    dslci.bindingCount = uint32_t(program->mDescriptorSetLayoutBings.size());
    dslci.pBindings = program->mDescriptorSetLayoutBings.data();

    vkCreateDescriptorSetLayout(xGetVulkanDevice(), &dslci, nullptr, &program->mDescriptorSetLayout);
}

void xInitDescriptorPool(XProgram *program)
{
    if (program->mWriteDescriptorSet.empty())
    {
        return;
    }

    VkDescriptorPoolCreateInfo dpci{};
    dpci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
    dpci.poolSizeCount = uint32_t(program->mDescriptorPoolSize.size());
    dpci.pPoolSizes = program->mDescriptorPoolSize.data();
    dpci.maxSets = 1;

    vkCreateDescriptorPool(xGetVulkanDevice(), &dpci, nullptr, &program->mDescriptorPool);
}

void xInitDescriptorSet(XProgram *program)
{
    if (program->mWriteDescriptorSet.empty())
    {
        return;
    }

    VkDescriptorSetAllocateInfo dsai{};
    dsai.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
    dsai.descriptorPool = program->mDescriptorPool;
    dsai.descriptorSetCount = 1;
    dsai.pSetLayouts = &program->mDescriptorSetLayout;

    vkAllocateDescriptorSets(xGetVulkanDevice(), &dsai, &program->mDescriptorSet);

    for (int i = 0; i < program->mWriteDescriptorSet.size(); ++i)
    {
        program->mWriteDescriptorSet[i].dstSet = program->mDescriptorSet;
    }

    vkUpdateDescriptorSets(xGetVulkanDevice(), uint32_t(program->mWriteDescriptorSet.size()), program->mWriteDescriptorSet.data(), 0, nullptr);
}

void xConfigUniformBuffer(XProgram *program, int binding, XBufferObject *ubo, VkShaderStageFlags shaderStageFlags)
{
    VkDescriptorSetLayoutBinding dslb{};
    dslb.binding = binding;
    dslb.descriptorCount = 1;
    dslb.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
    dslb.stageFlags = shaderStageFlags;

    program->mDescriptorSetLayoutBings.push_back(dslb);

    VkDescriptorPoolSize dps{};
    dps.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
    dps.descriptorCount = 1;

    program->mDescriptorPoolSize.push_back(dps);

    VkDescriptorBufferInfo *bufferInfo = new VkDescriptorBufferInfo();
    bufferInfo->offset = 0;
    bufferInfo->buffer = ubo->mBuffer;
    bufferInfo->range = ubo->GetSize();

    VkWriteDescriptorSet descriptorWriter{};
    descriptorWriter.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
    descriptorWriter.dstSet = program->mDescriptorSet;
    descriptorWriter.dstBinding = binding;
    descriptorWriter.dstArrayElement = 0;
    descriptorWriter.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
    descriptorWriter.descriptorCount = 1;
    descriptorWriter.pBufferInfo = bufferInfo;

    program->mWriteDescriptorSet.push_back(descriptorWriter);
}

void xGenImage(XTexture *texture, uint32_t w, uint32_t h, VkFormat f, VkImageUsageFlags usage, VkSampleCountFlagBits sample_count, int mipmap)
{
    VkImageCreateInfo ici{};
    ici.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
    ici.imageType = VK_IMAGE_TYPE_2D;
    ici.extent = {w, h, 1};
    ici.mipLevels = mipmap;
    ici.arrayLayers = 1;
    ici.format = f;
    ici.initialLayout = texture->mInitLayout;
    ici.usage = usage;
    ici.samples = sample_count;

    VkResult ret = vkCreateImage(xGetVulkanDevice(), &ici, nullptr, &texture->mImage);
    if (ret != VK_SUCCESS)
    {
        printf("failed to create image\n");
    }

    VkMemoryRequirements memory_requirements{};
    vkGetImageMemoryRequirements(xGetVulkanDevice(), texture->mImage, &memory_requirements);

    VkMemoryAllocateInfo mai{};
    mai.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
    mai.allocationSize = memory_requirements.size;
    mai.memoryTypeIndex = xGetMemoryType(memory_requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);

    vkAllocateMemory(xGetVulkanDevice(), &mai, nullptr, &texture->mMemory);

    vkBindImageMemory(xGetVulkanDevice(), texture->mImage, texture->mMemory, 0);
}

void xGenImageCube(XTexture *texture, uint32_t w, uint32_t h, VkFormat f, VkImageUsageFlags usage, VkSampleCountFlagBits sample_count, int mipmap)
{
    VkImageCreateInfo ici{};
    ici.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
    ici.imageType = VK_IMAGE_TYPE_2D;
    ici.extent = {w, h, 1};
    ici.mipLevels = mipmap;
    ici.arrayLayers = 6;
    ici.format = f;
    ici.initialLayout = texture->mInitLayout;
    ici.usage = usage;
    ici.samples = sample_count;
    ici.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;

    VkResult ret = vkCreateImage(xGetVulkanDevice(), &ici, nullptr, &texture->mImage);
    if (ret != VK_SUCCESS)
    {
        printf("failed to create image\n");
    }

    VkMemoryRequirements memory_requirements{};
    vkGetImageMemoryRequirements(xGetVulkanDevice(), texture->mImage, &memory_requirements);

    VkMemoryAllocateInfo mai{};
    mai.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
    mai.allocationSize = memory_requirements.size;
    mai.memoryTypeIndex = xGetMemoryType(memory_requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);

    vkAllocateMemory(xGetVulkanDevice(), &mai, nullptr, &texture->mMemory);

    vkBindImageMemory(xGetVulkanDevice(), texture->mImage, texture->mMemory, 0);
}

void xSubmitImage2D(XTexture *texture, int width, int height, const void *pixel)
{
    VkDeviceSize image_size = width * height;
    if (texture->mFormat == VK_FORMAT_R8G8B8A8_UNORM)
    {
        image_size *= 4;
    }

    VkBuffer tempBuffer;
    VkDeviceMemory tempMemory;
    xGenBuffer(tempBuffer, tempMemory, image_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);

    void *data;
    vkMapMemory(xGetVulkanDevice(), tempMemory, 0, image_size, 0, &data);
    memcpy(data, pixel, image_size);
    vkUnmapMemory(xGetVulkanDevice(), tempMemory);

    VkCommandBuffer commandBuffer;
    xBeginOneTimeCommandBuffer(&commandBuffer);

    VkImageSubresourceRange subResourceRange{};
    subResourceRange.aspectMask = texture->mImageAspectFlag;
    subResourceRange.baseMipLevel = 0;
    subResourceRange.levelCount = 1;
    subResourceRange.baseArrayLayer = 0;
    subResourceRange.layerCount = 1;

    xSetImageLayout(commandBuffer, texture->mImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, subResourceRange);

    VkBufferImageCopy copy{};
    copy.imageSubresource.aspectMask = texture->mImageAspectFlag;
    copy.imageSubresource.mipLevel = 0;
    copy.imageSubresource.baseArrayLayer = 0;
    copy.imageSubresource.layerCount = 1;
    copy.imageOffset = {0, 0, 0};
    copy.imageExtent = {static_cast<uint32_t>(width), static_cast<uint32_t>(height), 1};

    vkCmdCopyBufferToImage(commandBuffer, tempBuffer, texture->mImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copy);

    xSetImageLayout(commandBuffer, texture->mImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, subResourceRange);

    xEndOneTimeCommandBuffer(commandBuffer);

    vkDestroyBuffer(xGetVulkanDevice(), tempBuffer, nullptr);
    vkFreeMemory(xGetVulkanDevice(), tempMemory, nullptr);
}

void xSubmitImageCube(XTexture *texture, int width, int height, const void *pixel)
{
    VkDeviceSize offset_unit = width * height;
    if (texture->mFormat == VK_FORMAT_R8G8B8A8_UNORM)
    {
        offset_unit *= 4;
    }

    int imageSize = offset_unit * 6;

    VkBuffer tempBuffer;
    VkDeviceMemory tempMemory;
    xGenBuffer(tempBuffer, tempMemory, imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);

    void *data;
    vkMapMemory(xGetVulkanDevice(), tempMemory, 0, imageSize, 0, &data);
    memcpy(data, pixel, imageSize);
    vkUnmapMemory(xGetVulkanDevice(), tempMemory);

    VkCommandBuffer commandBuffer;
    xBeginOneTimeCommandBuffer(&commandBuffer);

    VkImageSubresourceRange subResourceRange{};
    subResourceRange.aspectMask = texture->mImageAspectFlag;
    subResourceRange.baseMipLevel = 0;
    subResourceRange.levelCount = 1;
    subResourceRange.baseArrayLayer = 0;
    subResourceRange.layerCount = 6;

    xSetImageLayout(commandBuffer, texture->mImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, subResourceRange);

    std::vector<VkBufferImageCopy> copies;
    for (uint32_t face = 0; face < 6; ++face)
    {
        VkBufferImageCopy copy{};
        copy.imageSubresource.aspectMask = texture->mImageAspectFlag;
        copy.imageSubresource.mipLevel = 0;
        copy.imageSubresource.baseArrayLayer = face;
        copy.imageSubresource.layerCount = 1;
        copy.imageOffset = {0, 0, 0};
        copy.imageExtent = {static_cast<uint32_t>(width), static_cast<uint32_t>(height), 1};
        copy.bufferOffset = offset_unit * face;

        copies.emplace_back(copy);
    }

    vkCmdCopyBufferToImage(commandBuffer, tempBuffer, texture->mImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 6, copies.data());

    xSetImageLayout(commandBuffer, texture->mImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, subResourceRange);

    xEndOneTimeCommandBuffer(commandBuffer);

    vkDestroyBuffer(xGetVulkanDevice(), tempBuffer, nullptr);
    vkFreeMemory(xGetVulkanDevice(), tempMemory, nullptr);
}

void xSetImageLayout(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout, VkImageSubresourceRange subresourceRange,
                     VkPipelineStageFlags src, VkPipelineStageFlags dst)
{
    VkImageMemoryBarrier barrier{};
    barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
    barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrier.oldLayout = oldLayout;
    barrier.newLayout = newLayout;
    barrier.image = image;
    barrier.subresourceRange = subresourceRange;

    xInitSrcAccessMask(oldLayout, barrier);
    xInitDstAccessMask(oldLayout, barrier);

    vkCmdPipelineBarrier(commandBuffer, src, dst, 0, 0, nullptr, 0, nullptr, 1, &barrier);
}

void xInitSrcAccessMask(VkImageLayout oldLayout, VkImageMemoryBarrier &barrier)
{
    switch (oldLayout)
    {
    case VK_IMAGE_LAYOUT_UNDEFINED:
        barrier.srcAccessMask = 0;
        break;
    case VK_IMAGE_LAYOUT_PREINITIALIZED:
        barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
        break;
    case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
        barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
        break;
    case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
        barrier.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
        break;
    case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
        barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
        break;
    case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
        barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
        break;
    case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
        barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
        break;
    default:
        printf("init src access mask: unprocessed\n");
        break;
    }
}

void xInitDstAccessMask(VkImageLayout newLayout, VkImageMemoryBarrier &barrier)
{
    switch (newLayout)
    {
    case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
        barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
        break;
    case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
        barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
        break;
    case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
        barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
        break;
    case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
        barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | barrier.dstAccessMask;
        break;
    case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
        if (barrier.srcAccessMask == 0)
        {
            barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
        }
        barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
        break;
    default:
        printf("init dst access mask: unprocessed\n");
        break;
    }
}

void xGenImageView2D(XTexture *texture, int mipmap)
{
    VkImageViewCreateInfo ivci{};
    ivci.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
    ivci.image = texture->mImage;
    ivci.viewType = VK_IMAGE_VIEW_TYPE_2D;
    ivci.format = texture->mFormat;
    ivci.subresourceRange.aspectMask = texture->mImageAspectFlag;
    ivci.subresourceRange.baseMipLevel = 0;
    ivci.subresourceRange.levelCount = mipmap;
    ivci.subresourceRange.baseArrayLayer = 0;
    ivci.subresourceRange.layerCount = 1;

    vkCreateImageView(xGetVulkanDevice(), &ivci, nullptr, &texture->mImageView);
}

void xGenImageViewCube(XTexture *texture, int mipmap)
{
    VkImageViewCreateInfo ivci{};
    ivci.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
    ivci.image = texture->mImage;
    ivci.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
    ivci.format = texture->mFormat;
    ivci.subresourceRange.aspectMask = texture->mImageAspectFlag;
    ivci.subresourceRange.baseMipLevel = 0;
    ivci.subresourceRange.levelCount = mipmap;
    ivci.subresourceRange.baseArrayLayer = 0;
    ivci.subresourceRange.layerCount = 6;
    ivci.components = {VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A};

    vkCreateImageView(xGetVulkanDevice(), &ivci, nullptr, &texture->mImageView);
}

void xGenSampler(XTexture *texture)
{
    VkSamplerCreateInfo samplerCreateInfo{};
    samplerCreateInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
    samplerCreateInfo.minFilter = texture->mMinFilter;
    samplerCreateInfo.magFilter = texture->mMagFilter;
    samplerCreateInfo.addressModeU = texture->mWrapU;
    samplerCreateInfo.addressModeV = texture->mWrapV;
    samplerCreateInfo.addressModeW = texture->mWrapW;
    samplerCreateInfo.anisotropyEnable = texture->mbEnableAnisotropy;
    samplerCreateInfo.maxAnisotropy = texture->mMaxAnisotropy;
    samplerCreateInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;

    vkCreateSampler(xGetVulkanDevice(), &samplerCreateInfo, nullptr, &texture->mSampler);
}

void xInitDefaultTexture()
{
    sDefaultTexture = new XTexture();
    sDefaultTexture->mFormat = VK_FORMAT_R8G8B8A8_UNORM;

    unsigned char *pixel = new unsigned char[256 * 256 * 4];
    memset(pixel, 255, sizeof(unsigned char) * 256 * 256 * 4);

    xGenImage(sDefaultTexture, 256, 256, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
    xSubmitImage2D(sDefaultTexture, 256, 256, pixel);
    xGenImageView2D(sDefaultTexture);
    xGenSampler(sDefaultTexture);

    delete[] pixel;
}

void xConfigSampler2D(XProgram *program, int binding, VkImageView imageView, VkSampler sampler, VkImageLayout layout)
{
    VkDescriptorSetLayoutBinding layoutBinding{};
    layoutBinding.binding = binding;
    layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
    layoutBinding.descriptorCount = 1;
    layoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
    layoutBinding.pImmutableSamplers = nullptr;

    program->mDescriptorSetLayoutBings.push_back(layoutBinding);

    VkDescriptorPoolSize poolSize{};
    poolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
    poolSize.descriptorCount = 1;

    program->mDescriptorPoolSize.push_back(poolSize);

    VkDescriptorImageInfo *imageInfo = new VkDescriptorImageInfo();
    imageInfo->imageLayout = layout;
    imageInfo->imageView = imageView;
    imageInfo->sampler = sampler;

    VkWriteDescriptorSet wds{};
    wds.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
    wds.dstSet = program->mDescriptorSet;
    wds.dstBinding = binding;
    wds.dstArrayElement = 0;
    wds.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
    wds.descriptorCount = 1;
    wds.pImageInfo = imageInfo;

    program->mWriteDescriptorSet.push_back(wds);
}

unsigned char *LoadImageFromFile(const char *path, int &width, int &height, int &channel, int force_channel, bool flipY)
{
    unsigned char *data = stbi_load(path, &width, &height, &channel, force_channel);
    if (data == nullptr)
    {
        return nullptr;
    }

    if (false == flipY)
    {
        for (int j = 0; j * 2 < height; ++j)
        {
            int index1 = j * width * channel;
            int index2 = (height - 1 - j) * width * channel;
            for (int i = width * channel; i > 0; --i)
            {
                unsigned char temp = data[index1];
                data[index1] = data[index2];
                data[index2] = temp;
                ++index1;
                ++index2;
            }
        }
    }
    return data;
}

XTexture *xGetDefaultTexture()
{
    return sDefaultTexture;
}

void xRebindUniformBuffer(XProgram *program, int binding, XUniformBuffer *ubo)
{
    VkDescriptorBufferInfo *bufferInfo = new VkDescriptorBufferInfo();
    bufferInfo->buffer = ubo->mBuffer;
    bufferInfo->offset = 0;
    if (ubo->mType == kXUniformBufferTypeMatrix)
    {
        bufferInfo->range = sizeof(XMatrix4x4f) * ubo->mMatrices.size();
    }
    else
    {
        bufferInfo->range = sizeof(XVector4f) * ubo->mVector4s.size();
    }

    delete program->mWriteDescriptorSet[binding].pBufferInfo;
    program->mWriteDescriptorSet[binding].pBufferInfo = bufferInfo;
    vkUpdateDescriptorSets(xGetVulkanDevice(), uint32_t(program->mWriteDescriptorSet.size()), program->mWriteDescriptorSet.data(), 0, nullptr);
}

void xRebindSampler(XProgram *program, int binding, VkImageView iv, VkSampler s, VkImageLayout layout)
{
    VkDescriptorImageInfo *imageInfo = new VkDescriptorImageInfo();
    imageInfo->imageView = iv;
    imageInfo->sampler = s;
    imageInfo->imageLayout = layout;

    delete program->mWriteDescriptorSet[binding].pImageInfo;
    program->mWriteDescriptorSet[binding].pImageInfo = imageInfo;
    vkUpdateDescriptorSets(xGetVulkanDevice(), uint32_t(program->mWriteDescriptorSet.size()), program->mWriteDescriptorSet.data(), 0, nullptr);
}

VkCommandBuffer xBeginRendering(VkCommandBuffer commandBuffer)
{
    VkCommandBuffer cmd;
    if (commandBuffer)
    {
        cmd = commandBuffer;
    }
    else
    {
        xBeginOneTimeCommandBuffer(&cmd);
    }

    VkFramebuffer render_target = xAquireRenderTarget();
    VkRenderPass render_pass = xGetGlobalRenderPass();

    VkClearValue clearValues[2]{};
    clearValues[0].color = {0.1f, 0.4f, 0.6f, 1.0f};
    clearValues[1].depthStencil = {1.0f, 0};

    VkRenderPassBeginInfo rpbi{};
    rpbi.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
    rpbi.framebuffer = render_target;
    rpbi.renderPass = render_pass;
    rpbi.renderArea.offset = {0, 0};
    rpbi.renderArea.extent = {uint32_t(xGetViewportWidth()), uint32_t(xGetViewportHeight())};
    rpbi.clearValueCount = 2;
    rpbi.pClearValues = clearValues;

    vkCmdBeginRenderPass(cmd, &rpbi, VK_SUBPASS_CONTENTS_INLINE);

    sMainCommandBuffer = cmd;

    return cmd;
}

void xEndRendering()
{
    vkCmdEndRenderPass(sMainCommandBuffer);
    vkEndCommandBuffer(sMainCommandBuffer);
}

static void xSubmitDrawCommand(VkCommandBuffer commandBuffer)
{
    VkSemaphore ready_to_render[] = {xGetReadyToRenderSemaphore()};
    VkSemaphore ready_to_present[] = {xGetReadyToPresentSemaphore()};

    VkSubmitInfo submitInfo{};
    submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;

    VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
    submitInfo.waitSemaphoreCount = 1;
    submitInfo.pWaitSemaphores = ready_to_render;
    submitInfo.pWaitDstStageMask = waitStages;
    submitInfo.pCommandBuffers = &commandBuffer;
    submitInfo.commandBufferCount = 1;
    submitInfo.signalSemaphoreCount = 1;
    submitInfo.pSignalSemaphores = ready_to_present;

    vkQueueSubmit(xGetGraphicQueue(), 1, &submitInfo, VK_NULL_HANDLE);
}

static void PresentFrameBuffer()
{
    VkSemaphore ready_to_present[] = {xGetReadyToPresentSemaphore()};
    VkSwapchainKHR swapChain = xGetSwapChain();

    uint32_t current_render_target_index = xGetCurrentRenderTargetIndex();

    VkPresentInfoKHR presentInfo{};
    presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
    presentInfo.pWaitSemaphores = ready_to_present;
    presentInfo.waitSemaphoreCount = 1;
    presentInfo.pSwapchains = &swapChain;
    presentInfo.swapchainCount = 1;
    presentInfo.pImageIndices = &current_render_target_index;

    vkQueuePresentKHR(xGetPresentQueue(), &presentInfo);
    vkQueueWaitIdle(xGetPresentQueue());
}

void xSwapBuffers(VkCommandBuffer commandBuffer)
{
    VkCommandBuffer cmd;
    if (commandBuffer)
    {
        cmd = commandBuffer;
    }
    else
    {
        cmd = sMainCommandBuffer;
    }

    xSubmitDrawCommand(cmd);
    PresentFrameBuffer();
    vkFreeCommandBuffers(xGetVulkanDevice(), xGetCommandPool(), 1, &cmd);

    sMainCommandBuffer = VK_NULL_HANDLE;
}

void xSetColorAttachmentCount(XFixedPipeline *pipeline, int count)
{
    pipeline->mColorBlendAttachmentStates.resize(count);
    for (int i = 0; i < count; ++i)
    {
        pipeline->mColorBlendAttachmentStates[i].colorWriteMask =
            VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
        pipeline->mColorBlendAttachmentStates[i].blendEnable = VK_FALSE;
        pipeline->mColorBlendAttachmentStates[i].srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
        pipeline->mColorBlendAttachmentStates[i].dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
        pipeline->mColorBlendAttachmentStates[i].srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
        pipeline->mColorBlendAttachmentStates[i].dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
        pipeline->mColorBlendAttachmentStates[i].colorBlendOp = VK_BLEND_OP_ADD;
        pipeline->mColorBlendAttachmentStates[i].alphaBlendOp = VK_BLEND_OP_ADD;
    }
}

void xEnableBlend(XFixedPipeline *pipeline, int attachment, VkBool32 enable)
{
    pipeline->mColorBlendAttachmentStates[attachment].blendEnable = enable;
}

void xBlend(XFixedPipeline *pipeline, int attachment, VkBlendFactor s_c, VkBlendFactor s_a, VkBlendFactor d_c, VkBlendFactor d_a)
{
    pipeline->mColorBlendAttachmentStates[attachment].srcColorBlendFactor = s_c;
    pipeline->mColorBlendAttachmentStates[attachment].srcAlphaBlendFactor = s_a;
    pipeline->mColorBlendAttachmentStates[attachment].dstColorBlendFactor = d_c;
    pipeline->mColorBlendAttachmentStates[attachment].dstAlphaBlendFactor = d_a;
}

void xBlendOp(XFixedPipeline *pipeline, int attachment, VkBlendOp color, VkBlendOp alpha)
{
    pipeline->mColorBlendAttachmentStates[attachment].colorBlendOp = color;
    pipeline->mColorBlendAttachmentStates[attachment].alphaBlendOp = alpha;
}

void xPolygonMode(XFixedPipeline *pipeline, VkPolygonMode mode)
{
    pipeline->mRasterizer.polygonMode = mode;
}

void xDisableRasterizer(XFixedPipeline *pipeline, VkBool32 disable)
{
    pipeline->mRasterizer.rasterizerDiscardEnable = disable;
}

void xEnableDepthTest(XFixedPipeline *pipeline, VkBool32 enable)
{
    pipeline->mDepthStencilState.depthTestEnable = enable;
}

void xInitPipelineLayout(XFixedPipeline *pipeline)
{
    VkPushConstantRange pushConstantRange{};
    pushConstantRange.stageFlags = pipeline->mPushConstantShaderStage;
    pushConstantRange.offset = 0;
    pushConstantRange.size = sizeof(XVector4f) * pipeline->mPushConstantCount;

    VkPipelineLayoutCreateInfo plci{};
    plci.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
    plci.pSetLayouts = pipeline->mDescriptorSetLayout;
    plci.setLayoutCount = pipeline->mDescriptorSetLayoutCount;
    plci.pPushConstantRanges = &pushConstantRange;
    plci.pushConstantRangeCount = 1;

    vkCreatePipelineLayout(xGetVulkanDevice(), &plci, nullptr, &pipeline->mPipelineLayout);
}

void xCreateFixedPipeline(XFixedPipeline *pipeline)
{
    const auto &bindingDescriptions = XVertexData::BindingDescription();
    const auto &attributeDescriptions = XVertexData::AttributeDescriptions();

    VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
    vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
    vertexInputInfo.vertexBindingDescriptionCount = 1;
    vertexInputInfo.pVertexBindingDescriptions = &bindingDescriptions;
    vertexInputInfo.vertexAttributeDescriptionCount = attributeDescriptions.size();
    vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();

    pipeline->mColorBlendState.attachmentCount = pipeline->mColorBlendAttachmentStates.size();
    pipeline->mColorBlendState.pAttachments = pipeline->mColorBlendAttachmentStates.data();

    VkDynamicState dynamicStates[] = {
        VK_DYNAMIC_STATE_VIEWPORT,
        VK_DYNAMIC_STATE_LINE_WIDTH,
        VK_DYNAMIC_STATE_SCISSOR,
        VK_DYNAMIC_STATE_DEPTH_BIAS,
    };
    VkPipelineDynamicStateCreateInfo pdsci{};
    pdsci.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
    pdsci.dynamicStateCount = 4;
    pdsci.pDynamicStates = dynamicStates;

    VkGraphicsPipelineCreateInfo pipelineCreateInfo{};
    pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
    pipelineCreateInfo.stageCount = pipeline->mShaderStageCount;
    pipelineCreateInfo.pStages = pipeline->mShaderStages;
    pipelineCreateInfo.pVertexInputState = &vertexInputInfo;
    pipelineCreateInfo.pInputAssemblyState = &pipeline->mInputAssetmlyState;
    pipelineCreateInfo.pViewportState = &pipeline->mViewportState;
    pipelineCreateInfo.pRasterizationState = &pipeline->mRasterizer;
    pipelineCreateInfo.pMultisampleState = &pipeline->mMultisampleState;
    pipelineCreateInfo.pDepthStencilState = &pipeline->mDepthStencilState;
    pipelineCreateInfo.pColorBlendState = &pipeline->mColorBlendState;
    pipelineCreateInfo.pDynamicState = &pdsci;
    pipelineCreateInfo.layout = pipeline->mPipelineLayout;
    pipelineCreateInfo.renderPass = pipeline->mRenderPass;
    pipelineCreateInfo.subpass = 0;
    pipelineCreateInfo.basePipelineHandle = VK_NULL_HANDLE;
    pipelineCreateInfo.basePipelineIndex = -1;

    vkCreateGraphicsPipelines(xGetVulkanDevice(), VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr, &pipeline->mPipeline);
}

void xSetDynamicState(XFixedPipeline *pipeline, VkCommandBuffer commandBuffer)
{
    vkCmdSetViewport(commandBuffer, 0, 1, &pipeline->mViewport);
    vkCmdSetScissor(commandBuffer, 0, 1, &pipeline->mScissor);
    vkCmdSetDepthBias(commandBuffer, pipeline->mDepthConstantFactor, pipeline->mDepthClamp, pipeline->mDepthSlopeFactor);
    vkCmdPushConstants(commandBuffer, pipeline->mPipelineLayout, pipeline->mPushConstantShaderStage, 0, sizeof(XVector4f) * pipeline->mPushConstantCount,
                       pipeline->mPushConstants);
}

void xInitGlobalRenderPass()
{
    VkSampleCountFlagBits sample_count = xGetGlobalFrameBufferSampleCount();
    VkAttachmentDescription colorAttachment{};
    colorAttachment.flags = 0;
    colorAttachment.format = xGetSwapChainImageFormat();
    colorAttachment.samples = sample_count;
    colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
    colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
    colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
    colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
    colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    colorAttachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

    if (sample_count == VK_SAMPLE_COUNT_1_BIT)
    {
        colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
    }

    VkAttachmentDescription depthAttachment{};
    depthAttachment.flags = 0;
    depthAttachment.format = VK_FORMAT_D24_UNORM_S8_UINT;
    depthAttachment.samples = sample_count;
    depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
    depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
    depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
    depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
    depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;

    VkAttachmentDescription colorAttachmentResolve{};
    colorAttachmentResolve.flags = 0;
    colorAttachmentResolve.format = xGetSwapChainImageFormat();
    colorAttachmentResolve.samples = VK_SAMPLE_COUNT_1_BIT;
    colorAttachmentResolve.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
    colorAttachmentResolve.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
    colorAttachmentResolve.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
    colorAttachmentResolve.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
    colorAttachmentResolve.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    colorAttachmentResolve.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;

    VkAttachmentReference colorAttachmentRef{};
    colorAttachmentRef.attachment = 0;
    colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

    VkAttachmentReference depthAttachmentRef{};
    depthAttachmentRef.attachment = 1;
    depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;

    VkAttachmentReference resolveAttachmentRef{};
    resolveAttachmentRef.attachment = 2;
    resolveAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

    VkAttachmentDescription attachments[3];
    int attachmentCount = 2;

    VkSubpassDescription subpass{};
    subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
    subpass.colorAttachmentCount = 1;
    subpass.pColorAttachments = &colorAttachmentRef;
    subpass.pDepthStencilAttachment = &depthAttachmentRef;

    if (sample_count == VK_SAMPLE_COUNT_1_BIT)
    {
        memcpy(&attachments[0], &colorAttachment, sizeof(VkAttachmentDescription));
        memcpy(&attachments[1], &depthAttachment, sizeof(VkAttachmentDescription));
    }
    else
    {
        subpass.pResolveAttachments = &resolveAttachmentRef;
        attachmentCount = 3;

        memcpy(&attachments[0], &colorAttachment, sizeof(VkAttachmentDescription));
        memcpy(&attachments[1], &depthAttachment, sizeof(VkAttachmentDescription));
        memcpy(&attachments[2], &colorAttachmentResolve, sizeof(VkAttachmentDescription));
    }

    VkRenderPassCreateInfo cpci{};
    cpci.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
    cpci.attachmentCount = attachmentCount;
    cpci.pAttachments = attachments;
    cpci.subpassCount = 1;
    cpci.pSubpasses = &subpass;

    vkCreateRenderPass(xGetVulkanDevice(), &cpci, nullptr, &sRenderPass);
}

void xInitSystemColorBuffer()
{
    sColorBuffer = new XTexture(VK_IMAGE_ASPECT_COLOR_BIT);
    sColorBuffer->mFormat = xGetSwapChainImageFormat();
    xGenImage(sColorBuffer, xGetViewportWidth(), xGetViewportHeight(), sColorBuffer->mFormat,
              VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, xGetGlobalFrameBufferSampleCount());
    xGenImageView2D(sColorBuffer);
}

void xInitSystemDepthBuffer()
{
    sDepthBuffer = new XTexture(VK_IMAGE_ASPECT_DEPTH_BIT);
    sDepthBuffer->mFormat = VK_FORMAT_D24_UNORM_S8_UINT;
    xGenImage(sDepthBuffer, xGetViewportWidth(), xGetViewportHeight(), sDepthBuffer->mFormat, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
              xGetGlobalFrameBufferSampleCount());
    xGenImageView2D(sDepthBuffer);
}

void xFrameBufferFinish(XSystemFrameBuffer *fbo)
{
    VkImageView render_targets[3];
    int render_target_count;
    if (fbo->mSampleCount != VK_SAMPLE_COUNT_1_BIT)
    {
        render_target_count = 3;
        render_targets[0] = fbo->mColorBuffer;
        render_targets[1] = fbo->mDepthBuffer;
        render_targets[2] = fbo->mResolveBuffer;
    }
    else
    {
        render_target_count = 2;
        render_targets[0] = fbo->mResolveBuffer;
        render_targets[1] = fbo->mDepthBuffer;
    }

    VkFramebufferCreateInfo fbci{};
    fbci.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
    fbci.renderPass = sRenderPass;
    fbci.pAttachments = render_targets;
    fbci.attachmentCount = render_target_count;
    fbci.width = xGetViewportWidth();
    fbci.height = xGetViewportHeight();
    fbci.layers = 1;

    vkCreateFramebuffer(xGetVulkanDevice(), &fbci, nullptr, &fbo->mFrameBuffer);
}

void xInitSystemFrameBuffer()
{
    if (sRenderPass == VK_NULL_HANDLE)
    {
        xInitGlobalRenderPass();
    }

    xInitSystemColorBuffer();
    xInitSystemDepthBuffer();

    if (sSystemFrameBuffer == VK_NULL_HANDLE)
    {
        sSystemFrameBuffer = new XSystemFrameBuffer[sFrameBufferCount];
    }

    VkSampleCountFlagBits sample_count = xGetGlobalFrameBufferSampleCount();
    int attachment_count = 2;
    if (sample_count != VK_SAMPLE_COUNT_1_BIT)
    {
        attachment_count = 3;
    }

    for (int i = 0; i < sFrameBufferCount; ++i)
    {
        sSystemFrameBuffer[i].mSampleCount = sample_count;
        sSystemFrameBuffer[i].mDepthBuffer = sDepthBuffer->mImageView;
        sSystemFrameBuffer[i].mResolveBuffer = sSwapChainImageViews[i];

        if (sample_count != VK_SAMPLE_COUNT_1_BIT)
        {
            sSystemFrameBuffer[i].mColorBuffer = sColorBuffer->mImageView;
        }
        xFrameBufferFinish(&sSystemFrameBuffer[i]);
    }
}

XVulkanHandle xGetSystemFrameBuffer(int index)
{
    return &sSystemFrameBuffer[index];
}

int xGetSystemFrameBufferCount()
{
    return sFrameBufferCount;
}

void xViewport(int width, int height)
{
    if (sViewportWidth != width || sViewportHeight != height)
    {
        sViewportWidth = width;
        sViewportHeight = height;

        delete sDepthBuffer;
        delete sColorBuffer;

        for (int i = 0; i < sFrameBufferCount; ++i)
        {
            vkDestroyFramebuffer(xGetVulkanDevice(), sSystemFrameBuffer[i].mFrameBuffer, nullptr);
            vkDestroyImageView(xGetVulkanDevice(), sSwapChainImageViews[i], nullptr);
        }

        vkDestroySwapchainKHR(xGetVulkanDevice(), sSwapChain, nullptr);
        xInitSwapChain();

        xInitSystemFrameBuffer();
    }
}

VkRenderPass xGetGlobalRenderPass()
{
    return sRenderPass;
}

void xInitCommandPool()
{
    VkCommandPoolCreateInfo ci{};
    ci.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
    ci.queueFamilyIndex = xGetGraphicQueueFamily();

    vkCreateCommandPool(xGetVulkanDevice(), &ci, nullptr, &sCommandPool);
}

VkCommandPool xGetCommandPool()
{
    return sCommandPool;
}

void xInitSemaphores()
{
    VkSemaphoreCreateInfo ci{};
    ci.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
    vkCreateSemaphore(xGetVulkanDevice(), &ci, nullptr, &sReadyToRender);
    vkCreateSemaphore(xGetVulkanDevice(), &ci, nullptr, &sReadyToPresent);
}

VkSemaphore xGetReadyToRenderSemaphore()
{
    return sReadyToRender;
}
VkSemaphore xGetReadyToPresentSemaphore()
{
    return sReadyToPresent;
}

void xInitSwapChain()
{
    uint32_t image_count = 2;
    VkSwapchainCreateInfoKHR ci{};
    ci.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
    ci.surface = sVulkanSurface;
    ci.minImageCount = image_count;
    ci.imageFormat = sSwapChainImageFormat;
    ci.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
    ci.imageExtent = {uint32_t(xGetViewportWidth()), uint32_t(xGetViewportHeight())};
    ci.imageArrayLayers = 1;
    ci.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;

    uint32_t queuefamilyIndices[] = {uint32_t(xGetGraphicQueueFamily()), uint32_t(xGetPresentQueueFamily())};
    if (xGetGraphicQueueFamily() != xGetPresentQueueFamily())
    {
        ci.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
        ci.pQueueFamilyIndices = queuefamilyIndices;
        ci.queueFamilyIndexCount = 2;
    }
    else
    {
        ci.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
        ci.pQueueFamilyIndices = queuefamilyIndices;
        ci.queueFamilyIndexCount = 1;
    }

    VkSurfaceCapabilitiesKHR capabilities;
    vkGetPhysicalDeviceSurfaceCapabilitiesKHR(sPhysicalDevice, sVulkanSurface, &capabilities);

    ci.preTransform = capabilities.currentTransform;
    ci.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
    ci.presentMode = VK_PRESENT_MODE_FIFO_KHR;
    ci.clipped = VK_TRUE;
    ci.oldSwapchain = VK_NULL_HANDLE;

    vkCreateSwapchainKHR(xGetVulkanDevice(), &ci, nullptr, &sSwapChain);

    vkGetSwapchainImagesKHR(xGetVulkanDevice(), sSwapChain, &image_count, nullptr);
    sFrameBufferCount = image_count;
    sSwapChainImages.resize(image_count);
    vkGetSwapchainImagesKHR(xGetVulkanDevice(), sSwapChain, &image_count, sSwapChainImages.data());

    sSwapChainExtent = {uint32_t(xGetViewportWidth()), uint32_t(xGetViewportHeight())};

    sSwapChainImageViews.resize(image_count);
    for (int i = 0; i < image_count; ++i)
    {
        VkImageViewCreateInfo ivci{};
        ivci.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
        ivci.image = sSwapChainImages[i];
        ivci.viewType = VK_IMAGE_VIEW_TYPE_2D;
        ivci.format = sSwapChainImageFormat;
        ivci.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
        ivci.subresourceRange.baseMipLevel = 0;
        ivci.subresourceRange.levelCount = 1;
        ivci.subresourceRange.baseArrayLayer = 0;
        ivci.subresourceRange.layerCount = 1;

        vkCreateImageView(xGetVulkanDevice(), &ivci, nullptr, &sSwapChainImageViews[i]);
    }
}

VkSwapchainKHR xGetSwapChain()
{
    return sSwapChain;
}

VkFormat xGetSwapChainImageFormat()
{
    return sSwapChainImageFormat;
}

VkDevice xGetVulkanDevice()
{
    return sVulkanDevice;
}

void xInitVulkanDevice()
{
    float priority = 1.0f;
    int queue_count = 2;

    VkDeviceQueueCreateInfo queue_create_info[2];
    queue_create_info[0] = {};
    queue_create_info[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
    queue_create_info[0].queueCount = 1;
    queue_create_info[0].queueFamilyIndex = xGetGraphicQueueFamily();
    queue_create_info[0].pQueuePriorities = &priority;

    queue_create_info[1] = {};
    queue_create_info[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
    queue_create_info[1].queueCount = 1;
    queue_create_info[1].queueFamilyIndex = xGetPresentQueueFamily();
    queue_create_info[1].pQueuePriorities = &priority;

    if (xGetGraphicQueueFamily() == xGetPresentQueueFamily())
    {
        queue_count = 1;
    }

    const char *extensions[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME};

    VkPhysicalDeviceFeatures features{};
    features.samplerAnisotropy = VK_TRUE;
    features.geometryShader = VK_TRUE;
    features.fillModeNonSolid = VK_TRUE;
    features.sampleRateShading = VK_TRUE;

    const char *layers[] = {"VK_LAYER_KHRONOS_validation"};

    VkDeviceCreateInfo ci{};
    ci.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
    ci.pQueueCreateInfos = queue_create_info;
    ci.queueCreateInfoCount = queue_count;
    ci.enabledExtensionCount = 1;
    ci.ppEnabledExtensionNames = extensions;
    ci.enabledLayerCount = 1;
    ci.ppEnabledLayerNames = layers;
    ci.pEnabledFeatures = &features;

    vkCreateDevice(xGetVulkanPhysicalDevice(), &ci, nullptr, &sVulkanDevice);

    vkGetDeviceQueue(sVulkanDevice, xGetGraphicQueueFamily(), 0, &sGraphicQueue);
    vkGetDeviceQueue(sVulkanDevice, xGetPresentQueueFamily(), 0, &sPresentQueue);
}

VkQueue xGetGraphicQueue()
{
    return sGraphicQueue;
}

VkQueue xGetPresentQueue()
{
    return sPresentQueue;
}

void xInitVulkanPhysicalDevice()
{
    uint32_t deviceCount = 0;
    vkEnumeratePhysicalDevices(sVulkanInstance, &deviceCount, nullptr);
    VkPhysicalDevice *devices = new VkPhysicalDevice[deviceCount];
    vkEnumeratePhysicalDevices(sVulkanInstance, &deviceCount, devices);

    for (uint32_t i = 0; i < deviceCount; ++i)
    {
        VkPhysicalDevice *device = &devices[i];

        VkPhysicalDeviceProperties properties;
        VkPhysicalDeviceFeatures features;
        vkGetPhysicalDeviceProperties(*device, &properties);
        vkGetPhysicalDeviceFeatures(*device, &features);

        uint32_t queue_family_count = 0;
        vkGetPhysicalDeviceQueueFamilyProperties(*device, &queue_family_count, nullptr);
        VkQueueFamilyProperties *queueFamilyProperties = new VkQueueFamilyProperties[queue_family_count];
        vkGetPhysicalDeviceQueueFamilyProperties(*device, &queue_family_count, queueFamilyProperties);

        sGraphicQueueFamily = -1;
        sPresentQueueFamily = -1;

        for (uint32_t j = 0; j < queue_family_count; ++j)
        {
            VkBool32 present_support = VK_FALSE;
            vkGetPhysicalDeviceSurfaceSupportKHR(*device, j, sVulkanSurface, &present_support);
            if (queueFamilyProperties[j].queueCount > 0 && present_support)
            {
                sPresentQueueFamily = j;
            }

            if (queueFamilyProperties[j].queueCount > 0 && queueFamilyProperties[j].queueFlags & VK_QUEUE_GRAPHICS_BIT)
            {
                sGraphicQueueFamily = j;
            }

            if (sGraphicQueueFamily != -1 && sPresentQueueFamily != -1)
            {
                sPhysicalDevice = *device;
                sMaxSampleCount = xGetMaxMSAASampleCount();
                break;
            }
        }
        delete[] queueFamilyProperties;

        if (sGraphicQueueFamily != -1 && sPresentQueueFamily != -1)
        {
            break;
        }
    }
    delete[] devices;
}

VkPhysicalDevice xGetVulkanPhysicalDevice()
{
    return sPhysicalDevice;
}

int xGetGraphicQueueFamily()
{
    return sGraphicQueueFamily;
}

int xGetPresentQueueFamily()
{
    return sPresentQueueFamily;
}

VkSampleCountFlagBits xGetMaxMSAASampleCount()
{
    VkPhysicalDeviceProperties physicalProperties;
    vkGetPhysicalDeviceProperties(sPhysicalDevice, &physicalProperties);

    VkSampleCountFlags count = physicalProperties.limits.framebufferColorSampleCounts > physicalProperties.limits.framebufferDepthSampleCounts
                                   ? physicalProperties.limits.framebufferDepthSampleCounts
                                   : physicalProperties.limits.framebufferColorSampleCounts;

    if (count & VK_SAMPLE_COUNT_64_BIT)
    {
        return VK_SAMPLE_COUNT_64_BIT;
    }
    else if (count & VK_SAMPLE_COUNT_32_BIT)
    {
        return VK_SAMPLE_COUNT_32_BIT;
    }
    else if (count & VK_SAMPLE_COUNT_16_BIT)
    {
        return VK_SAMPLE_COUNT_16_BIT;
    }
    else if (count & VK_SAMPLE_COUNT_8_BIT)
    {
        return VK_SAMPLE_COUNT_8_BIT;
    }
    else if (count & VK_SAMPLE_COUNT_4_BIT)
    {
        return VK_SAMPLE_COUNT_4_BIT;
    }
    else if (count & VK_SAMPLE_COUNT_2_BIT)
    {
        return VK_SAMPLE_COUNT_2_BIT;
    }
    return VK_SAMPLE_COUNT_1_BIT;
}

static void InitVulkanInstance()
{
    VkApplicationInfo appIfo{};
    appIfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
    appIfo.pApplicationName = "BattleFireEngine";
    appIfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
    appIfo.pEngineName = "Alice";
    appIfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
    appIfo.apiVersion = VK_API_VERSION_1_1;

    const char *extensions[] = {VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_WIN32_SURFACE_EXTENSION_NAME, VK_EXT_DEBUG_REPORT_EXTENSION_NAME};
    const char *layers[] = {"VK_LAYER_KHRONOS_validation"};

    VkInstanceCreateInfo ci{};
    ci.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
    ci.pApplicationInfo = &appIfo;
    ci.enabledExtensionCount = 3;
    ci.ppEnabledExtensionNames = extensions;
    ci.enabledLayerCount = 1;
    ci.ppEnabledLayerNames = layers;

    vkCreateInstance(&ci, nullptr, &sVulkanInstance);
}

static VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location,
                                                     int32_t messageCode, const char *pLayerPrefix, const char *pMessage, void *pUserData)
{
    printf("validation layer: %s\n", pMessage);
    return VK_FALSE;
}

void InitDebugger()
{
    VkDebugReportCallbackCreateInfoEXT ci{};
    ci.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
    ci.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT;
    ci.pfnCallback = debug_callback;

    __vkCreateDebugReportCallbackEXT(sVulkanInstance, &ci, nullptr, &sVulkanDebugger);
}

void InitSurface()
{
    VkWin32SurfaceCreateInfoKHR ci{};
    ci.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
    ci.hinstance = GetModuleHandle(nullptr);
    ci.hwnd = (HWND)sWindowHWND;

    __vkCreateWin32SurfaceKHR(sVulkanInstance, &ci, nullptr, &sVulkanSurface);
}

void xInitVulkan(void *param, int width, int height)
{
    sWindowHWND = param;
    sViewportWidth = width;
    sViewportHeight = height;

    InitVulkanInstance();

    __vkCreateDebugReportCallbackEXT = (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(sVulkanInstance, "vkCreateDebugReportCallbackEXT");
    __vkDestroyDebugReportCallbackEXT = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(sVulkanInstance, "vkDestroyDebugReportCallbackEXT");
    __vkCreateWin32SurfaceKHR = (PFN_vkCreateWin32SurfaceKHR)vkGetInstanceProcAddr(sVulkanInstance, "vkCreateWin32SurfaceKHR");

    InitDebugger();
    InitSurface();
    xInitVulkanPhysicalDevice();
    xInitVulkanDevice();
    xInitSwapChain();
    xInitSystemFrameBuffer();
    xInitCommandPool();
    xInitSemaphores();
    xInitDefaultTexture();
}

int xGetViewportWidth()
{
    return sViewportWidth;
}

int xGetViewportHeight()
{
    return sViewportHeight;
}

VkSampleCountFlagBits xGetGlobalFrameBufferSampleCount()
{
#if MSAA
    return sMaxSampleCount;
#else
    return VK_SAMPLE_COUNT_1_BIT;
#endif
}

VkFramebuffer xAquireRenderTarget()
{
    vkAcquireNextImageKHR(sVulkanDevice, sSwapChain, 1000000000, sReadyToRender, VK_NULL_HANDLE, &sCurrentRenderFrameBufferIndex);
    return sSystemFrameBuffer[sCurrentRenderFrameBufferIndex].mFrameBuffer;
}

uint32_t xGetCurrentRenderTargetIndex()
{
    return sCurrentRenderFrameBufferIndex;
}

unsigned char *xLoadFileContent(const char *path, int &filesize)
{
    unsigned char *fileContent = nullptr;
    filesize = 0;
    FILE *pFile = fopen(path, "rb");
    if (pFile)
    {
        fseek(pFile, 0, SEEK_END);
        int nLen = ftell(pFile);
        if (nLen > 0)
        {
            rewind(pFile);
            fileContent = new unsigned char[nLen + 1];
            fread(fileContent, sizeof(unsigned char), nLen, pFile);
            fileContent[nLen] = '\0';
            filesize = nLen;
        }
        fclose(pFile);
    }
    return fileContent;
}

XUniformBuffer::XUniformBuffer() : mBuffer(VK_NULL_HANDLE), mMemory(VK_NULL_HANDLE)
{
}

XUniformBuffer::~XUniformBuffer()
{
    if (mBuffer)
    {
        vkDestroyBuffer(xGetVulkanDevice(), mBuffer, nullptr);
    }

    if (mMemory)
    {
        vkFreeMemory(xGetVulkanDevice(), mMemory, nullptr);
    }
}

XProgram::XProgram()
    : mShaderStageCount(0), mVertexShader(VK_NULL_HANDLE), mFragmentShader(VK_NULL_HANDLE), mDescriptorPool(VK_NULL_HANDLE), mDescriptorSetLayout(VK_NULL_HANDLE),
      mDescriptorSet(VK_NULL_HANDLE)
{
    memset(mShaderStage, 0, sizeof(VkPipelineShaderStageCreateInfo) * 2);
}

XProgram::~XProgram()
{
    if (mVertexShader)
    {
        vkDestroyShaderModule(xGetVulkanDevice(), mVertexShader, nullptr);
    }

    if (mFragmentShader)
    {
        vkDestroyShaderModule(xGetVulkanDevice(), mFragmentShader, nullptr);
    }

    if (mDescriptorPool)
    {
        vkDestroyDescriptorPool(xGetVulkanDevice(), mDescriptorPool, nullptr);
    }

    if (mDescriptorSetLayout)
    {
        vkDestroyDescriptorSetLayout(xGetVulkanDevice(), mDescriptorSetLayout, nullptr);
    }

    for (int i = 0; i < mWriteDescriptorSet.size(); ++i)
    {
        VkWriteDescriptorSet *wds = &mWriteDescriptorSet[i];
        if (wds->pBufferInfo)
        {
            delete wds->pBufferInfo;
        }
        if (wds->pImageInfo)
        {
            delete wds->pImageInfo;
        }
    }
}

XTexture::XTexture(VkImageAspectFlags image_aspect) : mImageAspectFlag(image_aspect)
{
}

XTexture::~XTexture()
{
    if (mMemory)
    {
        vkFreeMemory(xGetVulkanDevice(), mMemory, nullptr);
    }
    if (mImage)
    {
        vkDestroyImage(xGetVulkanDevice(), mImage, nullptr);
    }
    if (mImageView)
    {
        vkDestroyImageView(xGetVulkanDevice(), mImageView, nullptr);
    }
    if (mSampler)
    {
        vkDestroySampler(xGetVulkanDevice(), mSampler, nullptr);
    }
}

XFixedPipeline::XFixedPipeline()
{
    mPipelineLayout = 0;
    mPipeline = 0;
    mInputAssetmlyState = {};
    mInputAssetmlyState.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
    mInputAssetmlyState.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
    mInputAssetmlyState.primitiveRestartEnable = VK_FALSE;
    mViewport = {};
    mScissor = {};
    mViewportState = {};
    mViewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
    mViewportState.viewportCount = 1;
    mViewportState.pViewports = &mViewport;
    mViewportState.scissorCount = 1;
    mViewportState.pScissors = &mScissor;
    mRasterizer = {};
    mRasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
    mRasterizer.depthClampEnable = VK_FALSE;
    mRasterizer.rasterizerDiscardEnable = VK_FALSE;
    mRasterizer.polygonMode = VK_POLYGON_MODE_FILL;
    mRasterizer.lineWidth = 1.0f;
    mRasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
    mRasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
    mRasterizer.depthBiasEnable = VK_TRUE;
    mRasterizer.depthBiasConstantFactor = 0.0f;
    mRasterizer.depthBiasClamp = 0.0f;
    mRasterizer.depthBiasSlopeFactor = 0.0f;
    mDepthStencilState = {};
    mDepthStencilState.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
    mDepthStencilState.depthTestEnable = VK_TRUE;
    mDepthStencilState.depthWriteEnable = VK_TRUE;
    mDepthStencilState.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
    mDepthStencilState.depthBoundsTestEnable = VK_FALSE;
    mDepthStencilState.minDepthBounds = 0.0f;
    mDepthStencilState.maxDepthBounds = 1.0f;
    mDepthStencilState.stencilTestEnable = VK_FALSE;
    mDepthStencilState.front = {};
    mDepthStencilState.back = {};
    mMultisampleState = {};
    mMultisampleState.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
    mMultisampleState.sampleShadingEnable = VK_TRUE;
    mMultisampleState.rasterizationSamples = xGetGlobalFrameBufferSampleCount();
    mMultisampleState.minSampleShading = 1.0f;
    mMultisampleState.pSampleMask = nullptr;
    mMultisampleState.alphaToCoverageEnable = VK_FALSE;
    mMultisampleState.alphaToOneEnable = VK_FALSE;
    mColorBlendState = {};
    mColorBlendState.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
    mColorBlendState.logicOpEnable = VK_FALSE;
    mColorBlendState.logicOp = VK_LOGIC_OP_COPY;
    mDescriptorSetLayout = nullptr;
    mShaderStages = nullptr;
    mShaderStageCount = 0;
    mDescriptorSetLayoutCount = 0;
    mRenderPass = 0;
    mSampleCount = xGetGlobalFrameBufferSampleCount();
    mPushConstantShaderStage = VK_SHADER_STAGE_FRAGMENT_BIT;
    mPushConstantCount = 8;
    mDepthConstantFactor = 0.0f;
    mDepthClamp = 0.0f;
    mDepthSlopeFactor = 0.0f;
}

XFixedPipeline::~XFixedPipeline()
{
    CleanUp();
}

void XFixedPipeline::CleanUp()
{
    if (mPipeline)
    {
        vkDestroyPipeline(xGetVulkanDevice(), mPipeline, nullptr);
    }

    if (mPipelineLayout)
    {
        vkDestroyPipelineLayout(xGetVulkanDevice(), mPipelineLayout, nullptr);
    }
}

void XVertexData::SetPosition(float x, float y, float z, float w)
{
    mPosition[0] = x;
    mPosition[1] = y;
    mPosition[2] = z;
    mPosition[3] = w;
}

void XVertexData::SetTexcoord(float x, float y, float z, float w)
{
    mTexcoord[0] = x;
    mTexcoord[1] = y;
    mTexcoord[2] = z;
    mTexcoord[3] = w;
}

void XVertexData::SetNormal(float x, float y, float z, float w)
{
    mNormal[0] = x;
    mNormal[1] = y;
    mNormal[2] = z;
    mNormal[3] = w;
}

void XVertexData::SetTangent(float x, float y, float z, float w)
{
    mTangent[0] = x;
    mTangent[1] = y;
    mTangent[2] = z;
    mTangent[3] = w;
}

const VkVertexInputBindingDescription &XVertexData::BindingDescription()
{
    static VkVertexInputBindingDescription binding_description{};
    binding_description.binding = 0;
    binding_description.stride = sizeof(XVertexData);
    binding_description.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
    return binding_description;
}

const std::vector<VkVertexInputAttributeDescription> &XVertexData::AttributeDescriptions()
{
    static std::vector<VkVertexInputAttributeDescription> attributesDescriptions;
    attributesDescriptions.resize(4);
    attributesDescriptions[0].binding = 0;
    attributesDescriptions[0].location = 0;
    attributesDescriptions[0].format = VK_FORMAT_R32G32B32A32_SFLOAT;
    attributesDescriptions[0].offset = 0;

    attributesDescriptions[1].binding = 0;
    attributesDescriptions[1].location = 1;
    attributesDescriptions[1].format = VK_FORMAT_R32G32B32A32_SFLOAT;
    attributesDescriptions[1].offset = sizeof(float) * 4;

    attributesDescriptions[2].binding = 0;
    attributesDescriptions[2].location = 2;
    attributesDescriptions[2].format = VK_FORMAT_R32G32B32A32_SFLOAT;
    attributesDescriptions[2].offset = sizeof(float) * 8;

    attributesDescriptions[3].binding = 0;
    attributesDescriptions[3].location = 3;
    attributesDescriptions[3].format = VK_FORMAT_R32G32B32A32_SFLOAT;
    attributesDescriptions[3].offset = sizeof(float) * 12;

    return attributesDescriptions;
}

void xVulkanCleanUp()
{
    if (sDefaultTexture != nullptr)
    {
        delete sDefaultTexture;
    }
    delete sColorBuffer;
    delete sDepthBuffer;
    delete[] sSystemFrameBuffer;

    for (int i = 0; i < sFrameBufferCount; ++i)
    {
        vkDestroyImageView(xGetVulkanDevice(), sSwapChainImageViews[i], nullptr);
    }

    vkDestroySwapchainKHR(xGetVulkanDevice(), sSwapChain, nullptr);

    vkDestroyRenderPass(xGetVulkanDevice(), sRenderPass, nullptr);

    vkDestroySemaphore(xGetVulkanDevice(), sReadyToRender, nullptr);
    vkDestroySemaphore(xGetVulkanDevice(), sReadyToPresent, nullptr);
    vkDestroyCommandPool(xGetVulkanDevice(), sCommandPool, nullptr);

    vkDestroyDevice(xGetVulkanDevice(), nullptr);
}

XSystemFrameBuffer::XSystemFrameBuffer()
{
    mFrameBuffer = VK_NULL_HANDLE;
    mColorBuffer = VK_NULL_HANDLE;
    mDepthBuffer = VK_NULL_HANDLE;
    mResolveBuffer = VK_NULL_HANDLE;
    mSampleCount = VK_SAMPLE_COUNT_1_BIT;
}

XSystemFrameBuffer::~XSystemFrameBuffer()
{
    if (mFrameBuffer)
    {
        vkDestroyFramebuffer(xGetVulkanDevice(), mFrameBuffer, nullptr);
    }
}
